package com.ruoyi.vote.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.UserType;
import com.ruoyi.common.exception.vote.VoteException;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.vote.admin.domain.VoteActivity;
import com.ruoyi.vote.admin.domain.VoteExamine;
import com.ruoyi.vote.admin.domain.bo.VoteExamineBo;
import com.ruoyi.vote.admin.domain.dto.VoteSignTicketDto;
import com.ruoyi.vote.admin.service.IVoteExamineService;
import com.ruoyi.vote.admin.service.IVoteSignService;
import com.ruoyi.vote.app.domain.request.VoteSignReq;
import com.ruoyi.vote.common.constants.ExamineConstants;
import com.ruoyi.vote.common.constants.ModelTypeConstants;
import com.ruoyi.vote.common.constants.VoteConstants;
import com.ruoyi.vote.common.strategy.ActivityContext;
import com.ruoyi.vote.common.strategy.SignCheck;
import com.ruoyi.vote.common.utils.CreateNumberUtil;
import com.ruoyi.vote.common.utils.KeyUtils;
import com.ruoyi.vote.common.utils.VoteCheckUtils;
import com.ruoyi.weixin.domain.WxUser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.ruoyi.vote.admin.domain.bo.VoteSignBo;
import com.ruoyi.vote.admin.domain.vo.VoteSignVo;
import com.ruoyi.vote.admin.domain.VoteSign;
import com.ruoyi.vote.admin.mapper.VoteSignMapper;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 活动 - 参赛选手管理Service业务层处理
 *
 * @author 筑巢
 * @date 2023-07-17
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class VoteSignServiceImpl extends ServiceImpl<VoteSignMapper, VoteSign> implements IVoteSignService {

    private final VoteSignMapper baseMapper;
    private final IVoteExamineService examineService;

    /**
     * 初始化选手
     */
    @Override
    public void loadingSignCache() {
        List<VoteSign> voteSigns = baseMapper.selectJoinSigns();
        if (CollUtil.isNotEmpty(voteSigns)){
            voteSigns.forEach(this::setCache);
        }
    }

    /**
     * 查询参赛选手详情
     */
    @Override
    public VoteSignVo queryById(Long id){
        VoteSignVo voteSignVo = baseMapper.selectVoById(id);
        if (ObjectUtil.isNull(voteSignVo)){
            throw new VoteException("vote.activity.user.doesNotExist");
        }
        if (! ExamineConstants.SUCCESS.equals(voteSignVo.getStatus())){
            throw new VoteException("vote.activity.user.doesNotExist");
        }
        List<VoteExamine> examineList = examineService.getExamineList(ModelTypeConstants.SIGN, id);
        if (CollUtil.isNotEmpty(examineList)){
            voteSignVo.setVoteExamines(examineList);
        }
        return voteSignVo;
    }

    /**
     * 查询选手账号信息
     *
     * @param aid
     */
    @Override
    public List<WxUser> queryWxUser(Long aid) {
        return baseMapper.selectWxUserByAid(aid);
    }

    /**
     * 获取排名选手
     *
     * @param aid
     * @param num 名词数量
     * @return
     */
    @Override
    public List<VoteSign> getRanking(Long aid, Integer num) {
        return baseMapper.selectList(new LambdaQueryWrapper<VoteSign>()
            .eq(VoteSign::getAId,aid)
            .eq(VoteSign::getStatus,ExamineConstants.SUCCESS)
            .orderByDesc(VoteSign::getTicket)
            .last("limit " + num));
    }

    /**
     * 查询参赛选手管理列表
     */
    @Override
    public TableDataInfo<VoteSignVo> queryPageList(VoteSignReq bo, PageQuery pageQuery) {
        QueryWrapper<VoteSign> lqw = buildQueryWrapper(bo);
        Page<VoteSignVo> result = baseMapper.selectPageVoList(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    private QueryWrapper<VoteSign> buildQueryWrapper(VoteSignReq bo) {
        // 是否审核,
        Map<String, Object> params = bo.getParams();
        QueryWrapper<VoteSign> lqw = Wrappers.query();
        lqw.eq("vs.del_flag", VoteConstants.VOTE_STATUS_SUCCESS);
        lqw.eq(bo.getAId() != null, "vs.a_id", bo.getAId());
        lqw.eq(bo.getNumber() != null, "vs.number", bo.getNumber());
        lqw.eq(bo.getMId() != null, "vs.m_id", bo.getMId());
        lqw.like(StringUtils.isNotBlank(bo.getName()), "vs.name", bo.getName());
        lqw.eq(StringUtils.isNotBlank(bo.getPhone()), "vs.phone", bo.getPhone());
        lqw.eq(bo.getTicket() != null, "vs.ticket", bo.getTicket());
        lqw.eq(bo.getRanking() != null, "vs.ranking", bo.getRanking());
        lqw.eq(bo.getStatus() != null, "vs.status", bo.getStatus());

        if (ObjectUtil.isNotEmpty(bo.getIsTotal())){
            lqw.orderByDesc("vs.ticket");
        }else if (ObjectUtil.isNotEmpty(bo.getIsWelcome())){
            lqw.orderByDesc("vs.welcome");
        }else if (ObjectUtil.isNotEmpty(bo.getIsNumber())){
            lqw.orderByDesc("vs.number");
        }
        lqw.orderByDesc("vs.create_time");
        return lqw;
    }

    /**
     * 新增参赛选手管理
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean insertByBo(VoteSignBo bo) {
        // 场景校验
        ActivityContext.setActivityStatus(new SignCheck());
        VoteActivity activity = ActivityContext.checkActivity(String.valueOf(bo.getAId()));

        VoteSign voteSign = BeanUtil.toBean(bo, VoteSign.class);
        voteSign.setTicket(0);
        voteSign.setShare(0);
        voteSign.setWelcome(0);
        voteSign.setRanking(0);
        voteSign.setDistanceUp(0);

        // 小程序校验
        if (ObjectUtil.isNull(bo.getMId()) && UserType.XCX_MEMBER.equals(LoginHelper.getUserType())){
            Long count = baseMapper.selectCount(new LambdaQueryWrapper<VoteSign>()
                .eq(VoteSign::getAId,bo.getAId())
                .eq(VoteSign::getMId,LoginHelper.getUserId())
                .ne(VoteSign::getStatus, ExamineConstants.ERROR)
                .ne(VoteSign::getStatus, ExamineConstants.CANCEL));
            if (count > 0){
                throw new VoteException("vote.activity.user.repeat.sign");
            }
            voteSign.setMId(LoginHelper.getUserId()); // 当前申请人
            voteSign.setStatus(ExamineConstants.REVIEW); // 审核中
        }

        // 选手编号
        Long number = CreateNumberUtil.generateContestantNumber(String.valueOf(activity.getId()),activity.getVoteStopTime());
        voteSign.setNumber(number);
        voteSign.setAType(activity.getAType());

        boolean flag = baseMapper.insert(voteSign) > 0;
        if (flag) {
            bo.setId(voteSign.getId());
            setCache(voteSign);
        }
        return flag;
    }

    /**
     * 修改参赛选手
     */
    @Override
    public Boolean updateByBo(VoteSign voteSign) {
        boolean flag = baseMapper.updateById(voteSign) > 0;
        if(flag) {
            setCache(voteSign);
        }
        return flag;
    }

    /**
     * 批量删除活动 - 参赛选手管理
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids) {
        return baseMapper.deleteBatchIds(ids) > 0;
    }

    /**
     * 同步选手票数以及排名
     */
    public void getEventRankings(String eventId) {
        log.info("--- start 计算排名 - 活动ID: [{}] ,排名时间:[{}]-----", eventId, DateUtils.getTime());

        String eventSignKey = KeyUtils.formatInfoSignKey(eventId);
        Map<String, VoteSignTicketDto> voteSignDtoHashMap = new HashMap<>();

        // 获取活动中每个选手的票数
        Set<String> cacheMapKeySet = RedisUtils.getCacheMapKeySet(eventSignKey);
        log.info("--- 计算排名 - 选手票数 - 活动ID: [{}] , 活动选手KEY:[{}] ,数据:{} -----", eventId, eventSignKey, cacheMapKeySet);
        for (String number : cacheMapKeySet) {
            String acsKey = KeyUtils.formatAcsKey(eventId, number);
            VoteSignTicketDto ticketDto = RedisUtils.getCacheMapValue(eventSignKey, number);
            Long votes = RedisUtils.getAtomicValue(acsKey);
            ticketDto.setTicket(Convert.toInt(votes));
            voteSignDtoHashMap.put(String.valueOf(number), ticketDto);
        }

        // 对选手票数进行排序
        List<String> signNumbers = voteSignDtoHashMap.entrySet().stream()
            .sorted(Map.Entry.comparingByValue(Comparator.comparing(VoteSignTicketDto::getTicket).reversed()))
            .map(entry -> String.valueOf(entry.getValue().getNumber()))
            .collect(Collectors.toList());

        // 计算排名并入库
        for (int i = 0; i < signNumbers.size(); i++) {
            String contestantNumber = signNumbers.get(i);
            VoteSignTicketDto voteSignDto = voteSignDtoHashMap.get(contestantNumber);
            Integer ticket = voteSignDto.getTicket();
            int rank = i + 1;
            Integer distanceToPrev = (i > 0) ? ticket - voteSignDtoHashMap.get(signNumbers.get(i - 1)).getTicket() : 0;

            voteSignDto.setTicket(ticket);
            voteSignDto.setRanking(rank);
            voteSignDto.setDistanceUp(distanceToPrev);

            // 更新选手投票数据
            VoteSign voteSign = BeanUtil.toBean(voteSignDto, VoteSign.class);
            log.info("--- 计算排名 - 更新选手投票数据 - 活动ID: [{}] , 更新数据:{} -----", eventId, voteSign);
            updateByBo(voteSign);
        }
        log.info("--- end 计算排名结束 - 活动ID: [{}] ,排名时间:[{}]-----", eventId,DateUtils.getTime());
    }

    /**
     * 报名审核
     *
     * @param bo
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean signExamine(VoteExamineBo bo) {
        VoteExamine add = BeanUtil.toBean(bo, VoteExamine.class);
        boolean flag = examineService.insert(add, ModelTypeConstants.SIGN);
        if (flag) {
            VoteSign voteSign = baseMapper.selectById(bo.getModelId());
            voteSign.setStatus(bo.getStatus());
            updateByBo(voteSign);
        }
        return flag;
    }

    /**
     * 选手热度+1
     * @param aid
     * @param mid
     */
    @Override
    public void updateWelcomeOne(Long aid, Long mid) {
        try {
            String infoEventKey = KeyUtils.formatInfoEventKey(String.valueOf(aid));
            VoteCheckUtils.activityCheck(infoEventKey);
            baseMapper.updateWelcomeOne(aid,mid);
            log.error("--- 活动进行中选手: [{}] 热度+1~",mid);
        }catch (VoteException e){
            log.error("--- 活动结束选手: [{}] 热度+0~",mid);
        }
    }

    /**
     * 设置选手票数缓存
     * @param voteSign
     */
    private void setCache(VoteSign voteSign){
        VoteSignTicketDto voteSignDto = BeanUtil.toBean(voteSign, VoteSignTicketDto.class);
        if (ObjectUtil.isNotNull(voteSignDto)){
            String key = KeyUtils.formatAcsKey(String.valueOf(voteSignDto.getAId()), String.valueOf(voteSignDto.getNumber()));
            String signKey = KeyUtils.formatInfoSignKey(String.valueOf(voteSignDto.getAId()));
            RedisUtils.deleteKeys(key);
            RedisUtils.delCacheMapValue(signKey,String.valueOf(voteSignDto.getNumber()));

            RedisUtils.setAtomicValue(key, voteSignDto.getTicket());
            RedisUtils.setCacheMapValue(signKey,String.valueOf(voteSignDto.getNumber()),voteSignDto);
        }
    }

    /**
     * 计算排名
     */
    private List<VoteSignVo> calculateRankings(List<VoteSignVo> list) {
        List<VoteSignVo> voteSignVos = new ArrayList<>();
        Map<Long, VoteSignVo> voteSignVoMap = list.stream()
            .collect(Collectors.toMap(VoteSignVo::getNumber, v -> v));
        // 对选手票数进行排序
        List<Long> signNumbers = voteSignVoMap.entrySet().stream()
            .sorted(Map.Entry.comparingByValue(Comparator.comparing(VoteSignVo::getTicket).reversed()))
            .map(entry -> entry.getValue().getNumber())
            .collect(Collectors.toList());
        // 计算排名并入库
        for (int i = 0; i < signNumbers.size(); i++) {
            VoteSignVo signVo = voteSignVoMap.get(signNumbers.get(i));
            Integer ticket = signVo.getTicket();
            int rank = i + 1;
            Integer distanceToPrev = (i > 0) ? ticket - voteSignVoMap.get(signNumbers.get(i - 1)).getTicket() : 0;
            signVo.setTicket(ticket);
            signVo.setRanking(rank);
            signVo.setDistanceUp(distanceToPrev);
            voteSignVos.add(signVo);
        }
        return voteSignVos;
    }
}
